/*
 *  Copyright (c) 2021@boostmerlin<https://github.com/boostmerlin/summer-boot>
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a copy
 *  of this software and associated documentation files (the "Software"), to deal
 *  in the Software without restriction, including without limitation the rights
 *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *  copies of the Software, and to permit persons to whom the Software is
 *  furnished to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in all
 *  copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 *  SOFTWARE.
 */

package io.summer.basis.tool;

import cn.hutool.core.bean.BeanUtil;
import io.summer.basis.excepts.AppException;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.cglib.beans.BeanCopier;
import org.springframework.context.ConfigurableApplicationContext;

import java.beans.PropertyDescriptor;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author Moonlight
 * 效率敏感其它更多功能，推荐使用MapStruct或者 MapperFacade(http://orika-mapper.github.io/orika-docs/intro.html)
 */
public final class BeanTool {
  /**
   * BeanCopier的缓存
   */
  private static final ConcurrentHashMap<String, BeanCopier> BEAN_COPIER_CACHE = new ConcurrentHashMap<>();

  public static <T> T copyProperties(Object source, T target) {
    BeanUtil.copyProperties(source, target);
    return target;
  }

  /**
   * 使用cglib来拷贝，需要source和target名称和类型完全一致，并且target属性>=source
   */
  public static <T> T copyPropertiesFast(Object source, T target) {
    String key = genKey(source.getClass(), target.getClass());
    BeanCopier beanCopier;
    if (BEAN_COPIER_CACHE.containsKey(key)) {
      beanCopier = BEAN_COPIER_CACHE.get(key);
    } else {
      beanCopier = BeanCopier.create(source.getClass(), target.getClass(), false);
      BEAN_COPIER_CACHE.put(key, beanCopier);
    }
    beanCopier.copy(source, target, null);
    return target;
  }

  public static <T> T copyProperties(Object source, T target, boolean ignoreNull) {
    if (ignoreNull) {
      BeanUtil.copyProperties(source, target, getNullPropertyNames(source));
      return target;
    } else {
      return copyProperties(source, target);
    }
  }

  private static String genKey(Class<?> srcClazz, Class<?> targetClazz) {
    return srcClazz.getName() + targetClazz.getName();
  }

  private static String[] getNullPropertyNames(Object object) {
    BeanWrapper beanWrapper = new BeanWrapperImpl(object);
    Set<String> nullNames = new HashSet<>();
    for (PropertyDescriptor pd : beanWrapper.getPropertyDescriptors()) {
      Object srcValue = beanWrapper.getPropertyValue(pd.getName());
      if (srcValue == null) {
        nullNames.add(pd.getName());
      }
    }
    return nullNames.toArray(new String[0]);
  }

  /**
   * 使用 applicationContext的注册方式，主要是启动后的动态注册，
   * 如果是启动前，推荐使用BeanDefinitionRegistryPostProcessor
   *
   * @param applicationContext
   * @param name
   * @param clazz
   * @param args
   */
  public static void registerBean(ConfigurableApplicationContext applicationContext, String name, Class<?> clazz,
                                  Object... args) {
    if (applicationContext.containsBean(name)) {
      Object bean = applicationContext.getBean(name);
      if (bean.getClass().isAssignableFrom(clazz)) {
        return;
      } else {
        throw new AppException("BeanName is duplicated: %s-%s", name, bean.getClass().getTypeName());
      }
    }

    BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
    for (Object arg : args) {
      beanDefinitionBuilder.addConstructorArgValue(arg);
    }
    BeanDefinition beanDefinition = beanDefinitionBuilder.getRawBeanDefinition();
    BeanDefinitionRegistry beanFactory = (BeanDefinitionRegistry) applicationContext.getBeanFactory();
    beanFactory.registerBeanDefinition(name, beanDefinition);
  }
}
